﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeRepositoryConsole.FCL
{
    /**
     * total order
       a binary relation on some set X , which is antisymmetric, transitive, 
       and total (this relation is denoted here by infix ≤ ). A set paired with 
       a total order is called a totally ordered set, a linearly ordered set, 
       a simply ordered set, or a chain.
       More symbolically, a set X is totally ordered under ≤ if the following statements hold 
       for all a , b and c  in X:

       If a ≤ b  and b ≤ a  then a = b  (antisymmetry);
       If a ≤ b  and b ≤ c  then a ≤ c  (transitivity);
       a ≤ b  or b ≤ a  (totality).

       In that situation there is no way to compare instances of two distinct derived classes, 
       because each is free to add unrelated properties that affect comparison, but which 
       cannot be compared against each other. If base parts are equal then we simply need to 
       devise a consistent comparison rule that will always return the same result. One simple 
       solution is just to compare full names of the two derived types. With this solution, all 
       instances of one derived class will be strictly less than all instances of another derived 
       class whenever their base parts are equal.

       Here is the comprehensive list of elements to include in implementation of CompareTo method in the base and derived classes:
       1.Implement protected virtual method CompareToValues which only compares values that are in common to this instance and instance passed as an argument. Base implementation compares only the relevant base fields. Return value of this method has the same meaning as return value of CompareTo method.
       2.Override CompareToValues in derived class. Overridden method should call base implementation and only if it returns zero should it compare additional fields defined by the derived class. This means that derived implementation is used only to break the ties among derived instances that have equal base fields. Breaking this rule would hurt the antisymmetry and transitivity principles.
       3.Implement CompareTypes helper method in the base class to perform comparison among instance types. If two instances are equal by value, then their types are compared. Base type is always less than derived type. Two unrelated types (i.e. distinct derived types) are compared by their full name to ensure consistency. CompareTypes method returns Int32 result with same meaning as for CompareTo method. This rule is important for preserving the antisymmetry principle.
       4.Implement CompareTo method in the base class to call CompareToValues; on zero result, tie would be broken by calling the CompareTypes helper method.
       5.Never throw exceptions from CompareTo or CompareToValues methods. Inability to perform comparison means that order introduced by the CompareTo method is not total.
       6.Make sure that null argument is treated correctly. Ensure that CompareToValues(null) returns positive value, indicating that all objects are greater than null.

     * see: http://codinghelmet.com/?path=howto/implement-icomparable-t
     * see: https://en.wikipedia.org/wiki/Total_order
     */

    public class Piano : IComparable<Piano>
    {
        public float Mark { get; set; }

        public int CompareTo(Piano other)
        {
            int result = CompareToValues(other);

            // If comparison base solely on values
            // returns zero, indicationg that two instance
            // are equal in those fields they have in common,
            // only then we break the tie by comparing
            // data types of the two instances.
            if (result == 0)
                result = CompareTypes(other);

            return result;
        }

        protected virtual int CompareToValues(Piano other)
        {
            if (object.ReferenceEquals(other, null))
                return 1; // All instance are greater than null

            // Base class simple compare Mark properties
            return Mark.CompareTo(other.Mark);
        }

        protected int CompareTypes(Piano other)
        {
            // Base type is considered less than derived type
            // when two instance have the same values of
            // base fields.

            // Instance of two distinct derived types are
            // ordered by comparing full names of their
            // types when base fields are equal.
            // This is consistent comparison rule for all
            // instance of two derived types.

            int result = 0;

            Type thisType = this.GetType();
            Type otherType = other.GetType();

            if (otherType.IsSubclassOf(thisType))
                result = -1;      // other is subclass of this class
            else if (thisType.IsSubclassOf(otherType))
                result = 1;       // this is subclass of other class
            else if (thisType != otherType)
                result = thisType.FullName.CompareTo(otherType.FullName);
            // cut the tie with a test that returns
            // the same value for all objects
            return result;
        }

        public override string ToString()
        {
            return string.Format("Mark={0:0.0}, Piano", Mark);
        }
    }

    public class GrandPiano : Piano
    {
        public string Producer { get; set; }

        protected override int CompareToValues(Piano other)
        {
            // Derived class must override this method if it has 
            // added fields that affect comparison result.
            // New fields are taken into account only if base class
            // find that base fields are equal.

            int result = base.CompareToValues(other);

            if (result == 0 && other is GrandPiano)
            {
                // typeof other exactly is GrandPiano

                GrandPiano gp = (GrandPiano)other;
                string thisProducer = Producer ?? string.Empty;
                string otherProducer = gp.Producer ?? string.Empty;

                result = thisProducer.CompareTo(otherProducer);
            }

            return result;
        }

        public override string ToString()
        {
            return string.Format("Mark={0:0.0}, Producer={1}， GrandPiano",
                Mark, Producer);
        }
    }

    public class PrivatePiano : Piano
    {
        public string Owner { get; set; }

        protected override int CompareToValues(Piano other)
        {
            int result = base.CompareToValues(other);

            if (result == 0 && other is PrivatePiano)
            {
                PrivatePiano pp = (PrivatePiano)other;
                string thisOwner = Owner ?? string.Empty;
                string otherOwner = pp.Owner ?? string.Empty;

                result = thisOwner.CompareTo(otherOwner);
            }

            return result;
        }

        public override string ToString()
        {
            return string.Format("Mark={0:0.0}, Owner={1}, PrivatePiano",
                Mark, Owner);
        }
    }

    public class IComparableTest
    {
        public void Test()
        {
            Piano[] pianos = new Piano[]
            {
                new Piano() {Mark=3.2F },
                new GrandPiano() {Mark=3.2F,Producer="Petzold" },
                new Piano() {Mark=3.4F },
                new PrivatePiano() {Mark=3.2F,Owner="Joe" },
                new GrandPiano() {Mark=3.2F,Producer="Schimmel" },
                new PrivatePiano() {Mark=3.4F,Owner="Henry" }
            };

            Array.Sort<Piano>(pianos);

            for (int i = 0; i < pianos.Length; i++)
                Console.WriteLine(pianos[i]);
        }
    }
}
